package oakclub;

import com.jolbox.bonecp.BoneCP;
import com.jolbox.bonecp.BoneCPConfig;
import it.sauronsoftware.cron4j.Scheduler;
import net.spy.memcached.AddrUtil;
import net.spy.memcached.BinaryConnectionFactory;
import net.spy.memcached.MemcachedClient;
import nf.fr.eraasoft.pool.ObjectPool;
import nf.fr.eraasoft.pool.PoolSettings;
import oakclub.background.CreateUserBackground;
import oakclub.background.LoginBackground;
import oakclub.background.ResetDailyBackground;
import oakclub.background.SendChatMessageBackground;
import oakclub.cronjob.BuildUserMappingJob;
import oakclub.cronjob.SyncMutualMatchWithChatListJob;
import oakclub.global.Config;
import oakclub.global.Container;
import oakclub.global.Enums;
import oakclub.pool.ScribeClientPool;
import oakclub.thrift.ThriftServer;
import oakclub.thrift.impl.APIImpl;
import oakclub.thrift.scribe.scribe;
import oakclub.thrift.service.API;
import org.gearman.Gearman;
import org.gearman.GearmanClient;
import org.gearman.GearmanServer;
import org.gearman.GearmanWorker;
import org.gearman.impl.GearmanImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main
{
    public static final String ENV_PROD = "prod";
    public static final String ENV_DEBUG = "debug";

    public String env = Main.ENV_DEBUG;
    private final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args)
    {
        Main main = new Main();
        main.logger.info("START");
        main.readArgs(args);
        main.addShutdownHook();
        main.readConfig();
        main.initConnectionPool();
        main.initMemcached();
        main.initGearman();
        main.initScribe();
        //main.initCronjob();
        main.startThrift();
    }

    private void readArgs(String[] args)
    {
        try
        {
            // env arg
            this.env = args[0];
            if(!this.env.equals(Main.ENV_DEBUG) && !this.env.equals(Main.ENV_PROD))
            {
                this.shutdown("env argument invalid");
            }
        }
        catch(Exception e)
        {
            this.shutdown(e);
        }
    }

    private void addShutdownHook()
    {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable()
        {
            public void run()
            {
                logger.info("IN SHUTDOWN HOOK");

                // close connection pool
                BoneCP connectionPool = (BoneCP) Container.getComponent(BoneCP.class);
                connectionPool.shutdown();
                logger.info("close connection pool");
            }
       }));
    }

    private void readConfig()
    {
        try
        {
            Config.read("config/main.properties");
        }
        catch(Exception e)
        {
            this.shutdown(e);
        }
    }

    private void initConnectionPool()
    {
        // get info from config
        String host = Config.get("mysql.host");
        int port = Integer.parseInt(Config.get("mysql.port"));
        String database = Config.get("mysql.database");
        String username = Config.get("mysql.username");
        String password = Config.get("mysql.password");
        int partitionCount = Integer.parseInt(Config.get("mysql.pool.partition.count"));
        int maxConnectionsPerPartition = Integer.parseInt(Config.get("mysql.pool.partition.connections.max"));
        int minConnectionsPerPartition = Integer.parseInt(Config.get("mysql.pool.partition.connections.min"));
        int acquireIncrement = Integer.parseInt(Config.get("mysql.pool.partition.acquire_increment"));
        int idleMaxAge = Integer.parseInt(Config.get("mysql.pool.idle_max_age"));

        // init
        BoneCPConfig poolConfig = new BoneCPConfig();
        poolConfig.setJdbcUrl(String.format("jdbc:mysql://%s:%d/%s", host, port, database));
        poolConfig.setUsername(username);
        poolConfig.setPassword(password);
        poolConfig.setPartitionCount(partitionCount);
        poolConfig.setMaxConnectionsPerPartition(maxConnectionsPerPartition);
        poolConfig.setMinConnectionsPerPartition(minConnectionsPerPartition);
        poolConfig.setAcquireIncrement(acquireIncrement);
        poolConfig.setIdleMaxAgeInSeconds(idleMaxAge);
        BoneCP connectionPool = null;
        try
        {
            connectionPool = new BoneCP(poolConfig);
        }
        catch(Exception e)
        {
            this.shutdown(e);
        }
        if(connectionPool != null)
        {
            // add to container
            Container.addComponent(Enums.Component.CONNECTION_POOL, connectionPool);
        }
        else
        {
            this.shutdown("connection pool init failed");
        }
    }

    private void initMemcached()
    {
        // get info from config
        String host = Config.get("memcached.host");
        int port = Integer.parseInt(Config.get("memcached.port"));

        // init
        MemcachedClient client = null;
        try
        {
            String cacheAddress = String.format("%s:%d", host, port);
            client = new MemcachedClient(new BinaryConnectionFactory(), AddrUtil.getAddresses(cacheAddress));
        }
        catch(Exception e)
        {
            this.shutdown(e);
        }
        if(client != null)
        {
            // add to container
            Container.addComponent(Enums.Component.MEMCACHE_CLIENT, client);
        }
        else
        {
            this.shutdown("memcached client init failed");
        }
    }

    private void initGearman()
    {
        // get info from config
        String host = Config.get("gearman.host");
        int port = Integer.parseInt(Config.get("gearman.port"));
        int numThreads = Integer.parseInt(Config.get("gearman.num_threads"));
        int numWorkers = Integer.parseInt(Config.get("gearman.num_workers"));
        String prefix = Config.get("gearman.prefix");

        // start
        Gearman gearman = null;
        try
        {
            gearman = new GearmanImpl(numThreads);
        }
        catch(Exception e)
        {
            this.shutdown(e);
        }
        if(gearman != null)
        {
            // init workers
            GearmanServer server = gearman.createGearmanServer(host, port);
            for(int i = 0; i < numWorkers; i++)
            {
                GearmanWorker worker = gearman.createGearmanWorker();
                worker.addFunction(prefix + LoginBackground.class.getSimpleName(), new LoginBackground());
                worker.addFunction(prefix + CreateUserBackground.class.getSimpleName(), new CreateUserBackground());
                worker.addFunction(prefix + ResetDailyBackground.class.getSimpleName(), new ResetDailyBackground());
                worker.addFunction(prefix + SendChatMessageBackground.class.getSimpleName(), new SendChatMessageBackground());
                if(!worker.addServer(server))
                {
                    this.shutdown("gearman worker add server failed");
                }
            }

            // init client
            GearmanClient client = gearman.createGearmanClient();
            if(!client.addServer(server))
            {
                this.shutdown("gearman client add server failed");
            }
            Container.addComponent(Enums.Component.GEARMAN_CLIENT, client);
        }
        else
        {
            this.shutdown("gearman init failed");
        }
    }

    private void initScribe()
    {
        // get info from config
        String host = Config.get("scribe.host");
        int port = Integer.parseInt(Config.get("scribe.port"));
        int maxClients = Integer.parseInt(Config.get("scribe.max_clients"));

        // init client pool
        PoolSettings<scribe.Client> poolSettings = new PoolSettings<scribe.Client>(new ScribeClientPool(host, port));
        poolSettings.min(0).max(maxClients);
        ObjectPool<scribe.Client> pool = poolSettings.pool();
        Container.addComponent(Enums.Component.SCRIBE_CLIENT, pool);
    }

    private void initCronjob()
    {
        Scheduler scheduler = new Scheduler();
        try
        {
            scheduler.schedule(BuildUserMappingJob.schedulingPattern, new BuildUserMappingJob());
            scheduler.schedule(SyncMutualMatchWithChatListJob.schedulingPattern, new SyncMutualMatchWithChatListJob());
            scheduler.start();
        }
        catch(Exception e)
        {
            this.shutdown(e);
        }
    }

    private void startThrift()
    {
        // get port from config
        int apiPort = Integer.parseInt(Config.get("thrift.port.api"));
        int utilityPort = Integer.parseInt(Config.get("thrift.port.utility"));

        // start
        new ThriftServer(new API.Processor<API.Iface>(new APIImpl()), apiPort).start();
        //new ThriftServer(new Utility.Processor<Utility.Iface>(new UtilityImpl()), utilityPort).start();
    }

    private void shutdown(Exception e)
    {
        this.logger.error("SHUTDOWN", e);
        System.exit(1);
    }

    private void shutdown(String msg)
    {
        this.logger.error("SHUTDOWN: {}", msg);
        System.exit(1);
    }
}
